﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Autodesk.AutoCAD.DatabaseServices;

namespace AutoCADDotNetLibrary
{
    /// <summary>
    /// RagApp的扩展
    /// </summary>
    public static class XDataExtention
    {
        /// <summary>
        /// 无内容则添加，有内容则修改，传入空参数则删除 
        /// </summary>
        /// <param name="obj">实体</param>
        /// <param name="regAppName">RagApp名字</param>
        /// <param name="values">XData的值集合(为空时删除)（不要求第一个必须带regAppName）</param>
        [Obsolete("请使用原生api，DBObject.XData")]
        public static void AddOrModifyOrDeleteXData(this DBObject obj, string regAppName, List<TypedValue> values)
        {
            //取得其他的扩展属性
            List<TypedValue> first = new List<TypedValue>();
            List<TypedValue> end = new List<TypedValue>();
            ResultBuffer res = obj.XData;
            if (res != null)
            {
                first = res.AsArray().TakeWhile(x => x.TypeCode == (int)DxfCode.ExtendedDataRegAppName && x.Value.ToString() == regAppName).ToList();
                end = res.AsArray().TakeWhile(x => x.TypeCode == (int)DxfCode.ExtendedDataRegAppName && x.Value.ToString() == regAppName).Skip(1)
                                   .TakeWhile(x => x.TypeCode == (int)DxfCode.ExtendedDataRegAppName).ToList();
            }

            if (values == null || values.Count == 0)//删除regAppNamedeXData
            {
                values = new List<TypedValue>()
                {
                    {  DxfCode.ExtendedDataRegAppName, regAppName  },
                };
            }
            else//添加或修改regAppNamedeXData
            {
                if (values.First().TypeCode != (int)DxfCode.ExtendedDataRegAppName)
                    values = (new List<TypedValue>() { { DxfCode.ExtendedDataRegAppName, regAppName } }).Concat(values).ToList();
            }

            obj.XData = new ResultBuffer(first.Concat(values).Concat(end).ToArray());
        }

        /// <summary>
        /// 得到扩展数据（第一个带regAppName），未找到为null
        /// </summary>
        /// <param name="obj">实体</param>
        /// <param name="regAppName">RagApp名字</param>
        /// <returns>扩展数据</returns>
        [Obsolete("请使用原生api，DBObject.GetXDataForApplication()")]
        public static List<TypedValue> GetXData(this DBObject obj, string regAppName)
        {
            if (obj.XData != null)
            {
                int i = 0;
                var group = obj.XData.AsArray()
                                     .Select(x => new { Index = x.TypeCode == (int)DxfCode.ExtendedDataRegAppName ? ++i : i, Data = x })
                                     .GroupBy(x => x.Index)
                                     .FirstOrDefault(x => x.First().Data.Value.ToString() == regAppName);

                return group?.Select(x => x.Data).ToList();
            }

            return null;
        }


        /// <summary>
        /// 集合初始值设定项中的扩展 Add 方法
        /// </summary>
        /// <param name="res"></param>
        /// <param name="dxfCode"></param>
        /// <param name="value"></param>
        /// <example>
        /// <code>
        /// ResultBuffer r = new ResultBuffer()
        /// {
        ///    { DxfCode.ExtendedDataAsciiString,"1" },
        /// };
        /// </code>
        /// </example>
        public static void Add(this ResultBuffer res, DxfCode dxfCode, object value)
        {
            res.Add(new TypedValue((int)dxfCode, value));
        }

        /// <summary>
        /// 集合初始值设定项中的扩展 Add 方法
        /// </summary>
        /// <param name="tvList"></param>
        /// <param name="dxfCode"></param>
        /// <param name="value"></param>
        /// <example>
        /// <code>
        /// List&lt;TypedValue&gt; r = new List&lt;TypedValue&gt;()
        /// {
        ///    { DxfCode.ExtendedDataAsciiString,"1" },
        /// };
        /// </code>
        /// </example>
        public static void Add(this ICollection<TypedValue> tvList, DxfCode dxfCode, object value)
        {
            tvList.Add(new TypedValue((int)dxfCode, value));
        }

        /// <summary>
        /// 集合初始值设定项中的扩展 Add 方法
        /// </summary>
        /// <param name="list"></param>
        /// <param name="obj">无意义参数，可以为任意值</param>
        /// <remarks>
        /// 由于存在<see cref="Autodesk.AutoCAD.DatabaseServices.ResultBuffer.Add(object)"/>的原因，这种形式不能使用一个参数，只能多种参数。
        /// </remarks>
        /// <example>
        /// <code>
        /// TypedValue[] list = new TypedValue[] { };
        /// ResultBuffer r = new ResultBuffer()
        /// {
        ///    { list,null },
        /// };
        /// </code>
        /// </example>
        public static void Add(this ResultBuffer res, IEnumerable<TypedValue> list, object obj)
        {
            foreach (TypedValue item in list)
            {
                res.Add(item);
            }
        }

        /// <summary>
        /// 集合初始值设定项中的扩展 Add 方法
        /// </summary>
        /// <param name="list"></param>
        /// <example>
        /// <code>
        /// TypedValue[] list = new TypedValue[] { };
        /// List&lt;TypedValue&gt; r = new List&lt;TypedValue&gt;()
        /// {
        ///    { list },
        /// };
        /// </code>
        /// </example>
        public static void Add(this ICollection<TypedValue> tvList, IEnumerable<TypedValue> list)
        {
            foreach (TypedValue item in list)
            {
                tvList.Add(item);
            }
        }


        /// <summary>
        /// key=模型类，value=设置值，Func参数：扩展数据的DBObject，模型，赋值是否成功
        /// </summary>
        private static Dictionary<Type, Func<DBObject, object, bool>> _modelSetValues = new Dictionary<Type, Func<DBObject, object, bool>>();
        /// <summary>
        /// Xdata转换模型
        /// </summary>
        /// <typeparam name="T">模型类型</typeparam>
        /// <param name="obj">实体对象</param>
        /// <param name="isThrowException">错误时是否抛出错误，默认不抛出错误</param>
        /// <returns>模型</returns>
        /// <remarks>
        /// 简单的XData和Model互相转换，<see cref="RegAppORMAttribute"/>为了取得XData数据的applicationName名称，不负责添加RegAppTable。
        /// <see cref="XDataORMAttribute"/>根据实体的XData位置，进行属性和具体数据的互相转换，
        /// Model的属性类型要和<see cref="DxfCode"/>相对应。
        /// 当<see cref="DxfCode.ExtendedDataAsciiString"/>时，支持<see cref="System.ComponentModel.TypeConverterAttribute"/>转换器
        /// </remarks>
        public static T GetModelByXdata<T>(this DBObject obj, bool isThrowException = false) where T : new()
        {
            try
            {
                Type t = typeof(T);
                if (!_modelSetValues.ContainsKey(t))
                {
                    RegAppORMAttribute reg = t.GetCustomAttributes(false).OfType<RegAppORMAttribute>().First();
                    var ps = t.GetProperties().Select(x => new { XDataORMAttribute = x.GetCustomAttributes(false).OfType<XDataORMAttribute>().FirstOrDefault(), PropertyInfo = x })
                        .Where(x => x.XDataORMAttribute != null).GroupBy(x => x.XDataORMAttribute.Index)
                        .Select(x => //只是为了检查是否有XDataORMAttribute.Index重复的数字
                        {
                            if (x.Count() != 1)
                            {
                                throw new Exception($"{nameof(XDataORMAttribute)}有重复的数字");
                            }
                            return x.First();
                        }).Select(x =>
                        {
                            //设置属性的转换器
                            Func<object, object> convertFrom = null;
                            if (x.PropertyInfo.PropertyType.GetInterface("System.Collections.IList") != null)//属性为数组的情况
                            {
                                convertFrom = value =>
                                {
                                    object[] values = ((IEnumerable)value).OfType<object>().Select(valueItem =>
                                    {
                                        if (valueItem == null)
                                            return null;
                                        if (x.XDataORMAttribute.DxfCode == DxfCode.ExtendedDataAsciiString)
                                        {
                                            TypeConverter typeConverter = TypeConverterHelper.GetConverterType(x.PropertyInfo, valueItem.GetType());
                                            if (typeConverter != null && typeConverter.CanConvertFrom(typeof(string)))
                                            {
                                                return valueItem == null ? null : typeConverter.ConvertFrom(valueItem);
                                            }
                                        }
                                        return valueItem;
                                    }).ToArray();

                                    //数组构造
                                    if (x.PropertyInfo.PropertyType.IsArray)
                                    {
                                        //创建数组对象
                                        IList arr = x.PropertyInfo.PropertyType.GetConstructors().First(y =>
                                        {
                                            ParameterInfo[] paras = y.GetParameters();
                                            return paras.Length == 1 && paras[0].ParameterType == typeof(int);
                                        }).Invoke(new object[] { values.Length }) as IList;

                                        for (int i = 0; i < values.Length; i++)
                                        {
                                            arr[i] = values[i];
                                        }
                                        return arr;
                                    }
                                    else//Ilist
                                    {
                                        IList arr = x.PropertyInfo.PropertyType.GetConstructors().First(y =>
                                        {
                                            ParameterInfo[] paras = y.GetParameters();
                                            return paras.Length == 0;
                                        }).Invoke(null) as IList;

                                        for (int i = 0; i < values.Length; i++)
                                        {
                                            arr.Add(values[i]);
                                        }
                                        return arr;
                                    }
                                };
                            }
                            else if (x.XDataORMAttribute.DxfCode == DxfCode.ExtendedDataAsciiString)
                            {
                                TypeConverter typeConverter = TypeConverterHelper.GetConverterType(x.PropertyInfo);
                                if (typeConverter != null && typeConverter.CanConvertFrom(typeof(string)))
                                {
                                    convertFrom = value => value == null ? null : typeConverter.ConvertFrom(value);
                                }
                            }
                            else
                            {
                                convertFrom = value => value;
                            }

                            return new { XDataORMAttribute = x.XDataORMAttribute, PropertyInfo = x.PropertyInfo, ConvertFrom = convertFrom };
                        }).ToList();

                    Func<DBObject, object, bool> action = (a, b) =>
                    {
                        ResultBuffer xdata = a.GetXDataForApplication(reg.RegAppTableName);

                        if (xdata == null)//赋值失败
                            return false;

                        //如果遇到在"&*-*&"中间的为数组，处理数组问题。
                        TypedValue[] allTypedValue = xdata.Cast<TypedValue>().ToArray();
                        List<object> value = new List<object>();
                        Func<TypedValue, bool> func = x => x.TypeCode == (short)DxfCode.ExtendedDataAsciiString && x.Value?.ToString() == "&*-*&";
                        while (allTypedValue.Length != 0)
                        {
                            TypedValue[] first = allTypedValue.TakeWhile(x => !func(x)).ToArray();
                            TypedValue[] mid = allTypedValue.SkipWhile(x => !func(x)).Skip(1).TakeWhile(x => !func(x)).ToArray();
                            allTypedValue = allTypedValue.SkipWhile(x => !func(x)).Skip(1).SkipWhile(x => !func(x)).Skip(1).ToArray();

                            value.AddRange(first.Select(x => x.Value));//不是数组
                            value.Add(mid.Select(x => x.Value).ToArray());//此为数组
                        }

                        //调用转换器，找到扩展数据的第i+1个位置的数据
                        ps.ForEach(x => x.PropertyInfo.SetValue(b, x.ConvertFrom?.Invoke(value.Skip(x.XDataORMAttribute.Index + 1).FirstOrDefault()), null));

                        return true;//赋值成功
                    };

                    _modelSetValues.Add(t, action);
                }

                T instance = new T();
                bool result = _modelSetValues[t](obj, instance);

                if (result == true)
                {
                    return instance;//赋值成功
                }
                else
                {
                    return default;//赋值失败
                }
            }
            catch
            {
                if (isThrowException)
                {
                    throw;
                }
                else
                {
                    return default;
                }
            }
        }


        /// <summary>
        /// key=模型类，value=设置xdata，Func参数：扩展数据的DBObject，模型
        /// </summary>
        private static Dictionary<Type, Action<DBObject, object>> _modelSetXdata = new Dictionary<Type, Action<DBObject, object>>();
        /// <summary>
        /// 设置Xdata
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="model"></param>
        /// <remarks>
        /// 简单的XData和Model互相转换，<see cref="RegAppORMAttribute"/>为了取得XData数据的applicationName名称，不负责添加RegAppTable。
        /// <see cref="XDataORMAttribute"/>根据实体的XData位置，进行属性和具体数据的互相转换，
        /// Model的属性类型要和<see cref="DxfCode"/>相对应。
        /// 当<see cref="DxfCode.ExtendedDataAsciiString"/>时，支持<see cref="System.ComponentModel.TypeConverterAttribute"/>转换器
        /// </remarks>
        public static void SetXdataByModel<T>(this DBObject obj, T model) where T : class
        {
            Type t = typeof(T);
            if (!_modelSetXdata.ContainsKey(t))
            {
                RegAppORMAttribute reg = t.GetCustomAttributes(false).OfType<RegAppORMAttribute>().First();
                var ps = t.GetProperties().Select(x => new { XDataORMAttribute = x.GetCustomAttributes(false).OfType<XDataORMAttribute>().FirstOrDefault(), PropertyInfo = x })
                    .Where(x => x.XDataORMAttribute != null).GroupBy(x => x.XDataORMAttribute.Index)
                    .Select(x => //只是为了检查是否有XDataORMAttribute.Index重复的数字
                    {
                        if (x.Count() != 1)
                        {
                            throw new Exception($"{nameof(XDataORMAttribute)}有重复的数字");
                        }
                        return x.First();
                    }).Where(x => x.XDataORMAttribute.Index >= 0).OrderBy(x => x.XDataORMAttribute.Index)
                    .Select(x =>
                    {
                        //属性为数组的情况
                        Func<object, object[]> arrConvertTo = null;
                        //设置属性的转换器
                        Func<object, object> convertTo = null;
                        if (x.PropertyInfo.PropertyType.GetInterface("System.Collections.IList") != null)//数组
                        {
                            arrConvertTo = value =>
                            {
                                if (value == null)
                                    return new object[] { };

                                return ((IEnumerable)value).OfType<object>().Select(valueItem =>
                                {
                                    if (valueItem == null)
                                        return null;
                                    if (x.XDataORMAttribute.DxfCode == DxfCode.ExtendedDataAsciiString)
                                    {
                                        Type itemAss = valueItem.GetType();
                                        TypeConverter typeConverter = TypeConverterHelper.GetConverterType(x.PropertyInfo, itemAss);
                                        if (typeConverter != null && typeConverter.CanConvertTo(itemAss))
                                        {
                                            return typeConverter.ConvertTo(valueItem, itemAss);
                                        }
                                    }
                                    return valueItem;
                                }).ToArray();
                            };
                        }
                        else
                        {
                            if (x.XDataORMAttribute.DxfCode == DxfCode.ExtendedDataAsciiString)
                            {
                                TypeConverter typeConverter = TypeConverterHelper.GetConverterType(x.PropertyInfo);
                                if (typeConverter != null && typeConverter.CanConvertTo(x.PropertyInfo.PropertyType))
                                {
                                    convertTo = value => value == null ? null : typeConverter.ConvertTo(value, x.PropertyInfo.PropertyType);
                                }
                            }
                            if (convertTo == null)
                            {
                                convertTo = value => value;
                            }
                        }
                        return new { XDataORMAttribute = x.XDataORMAttribute, PropertyInfo = x.PropertyInfo, ConvertTo = convertTo, ArrConvertTo = arrConvertTo };
                    }).ToList();

                if (ps.Count == 0)
                {
                    _modelSetXdata.Add(t, null);
                    return;
                }

                int max = ps.Max(x => x.XDataORMAttribute.Index);

                Action<DBObject, object> action = (a, b) =>
                {
                    ResultBuffer xdata = new ResultBuffer();
                    xdata.Add(new TypedValue((int)DxfCode.ExtendedDataRegAppName, reg.RegAppTableName));

                    if (b != null)
                    {
                        Enumerable.Range(0, max + 1).ToList().ForEach(x =>
                        {
                            var first = ps.FirstOrDefault(y => y.XDataORMAttribute.Index == x);

                            if (first != null)
                            {
                                if (first.ConvertTo != null)
                                {
                                    //属性不是是数组
                                    xdata.Add(new TypedValue((int)first.XDataORMAttribute.DxfCode, first.ConvertTo(first.PropertyInfo.GetValue(b, null))));
                                }
                                else
                                {
                                    xdata.Add(new TypedValue((int)DxfCode.ExtendedDataAsciiString, "&*-*&"));
                                    //属性是数组
                                    foreach (object item in first.ArrConvertTo(first.PropertyInfo.GetValue(b, null)))
                                    {
                                        xdata.Add(new TypedValue((int)first.XDataORMAttribute.DxfCode, item));
                                    }
                                    xdata.Add(new TypedValue((int)DxfCode.ExtendedDataAsciiString, "&*-*&"));
                                }
                            }
                            else
                            {
                                xdata.Add(new TypedValue((int)DxfCode.ExtendedDataAsciiString, null));//空值，默认值
                            }
                        });
                    }
                    a.XData = xdata;
                };

                _modelSetXdata.Add(t, action);
            }

            _modelSetXdata[t]?.Invoke(obj, model);
        }

        private static class TypeConverterHelper
        {
            /// <summary>
            /// 得到属性的返回类型的转换器
            /// </summary>
            /// <param name="propertyInfo"></param>
            /// <param name="arrayItemType"></param>
            /// <returns></returns>
            internal static TypeConverter GetConverterType(PropertyInfo propertyInfo, Type arrayItemType = null)
            {
                //https://github.com/dotnet/wpf/blob/89d172db0b7a192de720c6cfba5e28a1e7d46123/src/Microsoft.DotNet.Wpf/src/Shared/System/Windows/Markup/TypeConverterHelper.cs

                Type converterType = null;
                //属性上的转换器
                TypeConverterAttribute tc1 = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true).OfType<TypeConverterAttribute>().FirstOrDefault();
                if (tc1 != null)
                {
                    converterType = Type.GetType(tc1.ConverterTypeName);
                }
                //属性的类上的转换器
                TypeConverterAttribute tc2 = propertyInfo.PropertyType.GetCustomAttributes(typeof(TypeConverterAttribute), true).OfType<TypeConverterAttribute>().FirstOrDefault();
                if (tc2 != null)
                {
                    converterType = Type.GetType(tc2.ConverterTypeName);
                }


                Type type = arrayItemType ?? propertyInfo.PropertyType;
                if (type.IsEnum)
                {
                    converterType = typeof(System.ComponentModel.EnumConverter);
                }
                else if (typeof(Int32).IsAssignableFrom(type))
                {
                    converterType = typeof(Int32Converter);
                }
                else if (typeof(Int16).IsAssignableFrom(type))
                {
                    converterType = typeof(Int16Converter);
                }
                else if (typeof(Int64).IsAssignableFrom(type))
                {
                    converterType = typeof(Int64Converter);
                }
                else if (typeof(UInt32).IsAssignableFrom(type))
                {
                    converterType = typeof(UInt32Converter);
                }
                else if (typeof(UInt16).IsAssignableFrom(type))
                {
                    converterType = typeof(UInt16Converter);
                }
                else if (typeof(UInt64).IsAssignableFrom(type))
                {
                    converterType = typeof(UInt64Converter);
                }
                else if (typeof(Boolean).IsAssignableFrom(type))
                {
                    converterType = typeof(BooleanConverter);
                }
                else if (typeof(Double).IsAssignableFrom(type))
                {
                    converterType = typeof(DoubleConverter);
                }
                else if (typeof(Single).IsAssignableFrom(type))
                {
                    converterType = typeof(SingleConverter);
                }
                else if (typeof(Byte).IsAssignableFrom(type))
                {
                    converterType = typeof(ByteConverter);
                }
                else if (typeof(SByte).IsAssignableFrom(type))
                {
                    converterType = typeof(SByteConverter);
                }
                else if (typeof(Char).IsAssignableFrom(type))
                {
                    converterType = typeof(CharConverter);
                }
                else if (typeof(Decimal).IsAssignableFrom(type))
                {
                    converterType = typeof(DecimalConverter);
                }
                else if (typeof(TimeSpan).IsAssignableFrom(type))
                {
                    converterType = typeof(TimeSpanConverter);
                }
                else if (typeof(Guid).IsAssignableFrom(type))
                {
                    converterType = typeof(GuidConverter);
                }
                else if (typeof(String).IsAssignableFrom(type))
                {
                    converterType = typeof(StringConverter);
                }
                else if (typeof(DateTime).IsAssignableFrom(type))
                {
                    converterType = typeof(DateTimeConverter);
                }

                return converterType.GetConstructor(new Type[] { }).Invoke(null) as TypeConverter;
            }
        }
    }
}
